#include <time.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdbool.h>
#include <argp.h>
#include <signal.h>
#include <sys/mman.h>

#include "main.h"
#include "msgid.h"
#include "fim.h"

/*
//|----------------------------------------------------------------------------
//| Defines
//|----------------------------------------------------------------------------
*/
#ifndef MAX
#define MAX(a,b) ((a>b)?a:b)
#endif

/*
//|----------------------------------------------------------------------------
//| Local variables
//|----------------------------------------------------------------------------
*/
static struct argp_option eec_argp_options[] =
{
    MAIN_ARGP_OPTIONS,
    { 0,0,0,0,0,0 }
};

struct eec_options {
    char vmajor;
    char vminor;
    bool timestamp;
    int default_log_level;
    int default_stdout_msg;
    int local_fd;
    int alarm_fd;
    int remote_fd;
    struct sockaddr_un  local_addr;
    struct sockaddr_un  alarm_addr;
    struct sockaddr_in  remote_addr;
    int  scheduling;
    int prio_max;
    int prio_min;
    char * alarm_path;
    char * app_path;
    char * fd_path;
    uint16_t port;
    char * host;
};

static struct eec_options options =
{
    .vmajor             = 0,
    .vminor             = 1,
    .timestamp          = true,
    .default_log_level  = LOG_INFO,
    .default_stdout_msg = 0,
    .local_fd           = 0,
    .alarm_fd           = 0,
    .remote_fd          = 0,
    .scheduling         = SCHED_OTHER,
    .prio_max           = 0,
    .prio_min           = 0,
    .port               = 0,
    .fd_path            = 0,
    .alarm_path         = 0
};

/*
//|--------------------------------------------------------------------------
//| Method     : timestamp_fct
//|--------------------------------------------------------------------------
*/
static __inline__ void timestamp_fct
(
    struct timeval * x
)
{
    if( options.timestamp )
    {
        (void)gettimeofday(x,0);
    }
}

/*
//|--------------------------------------------------------------------------
//| Method     : parse_opt
//|--------------------------------------------------------------------------
*/
static error_t
parse_opt(int key, char* arg, struct argp_state *state)
{
    /*lint --e{668} suppress "arg" is not NULL check */
    /* Get the INPUT argument from `argp_parse', which we
       know is a pointer to our structure. */
    if( state == NULL )
    {
        printf("the pointer of argp_state is null \n");
        return -1;
    }
    struct eec_options *arguments = state->input;

    switch( key )
    {
        case MAIN_ARG_TIMESTAMP_KEY:
            arguments->timestamp = false;
            break;

        case MAIN_ARG_VERBOSE_KEY:
            arguments->default_log_level  = LOG_DEBUG;
            arguments->default_stdout_msg = LOG_PERROR;
            break;

        case MAIN_ARG_SCHEDULING_KEY:
            if( strcmp(arg, "FIFO") == 0 )
            {
                arguments->scheduling = SCHED_FIFO;
            }
            else if( strcmp(arg, "RR") == 0 )
            {
                arguments->scheduling = SCHED_RR;
            }
            break;

        case MAIN_ARG_PRIO_MAX_KEY:
            arguments->prio_max = strtoul(arg, NULL, 0);
            break;

        case MAIN_ARG_PRIO_MIN_KEY:
            arguments->prio_min = strtoul(arg, NULL, 0);
            break;

        case MAIN_ARG_FD_KEY :
            arguments->fd_path = arg;
            break;

        case MAIN_ARG_PORT_KEY:
            arguments->port = strtoul(arg, NULL, 0);
            break;

        case MAIN_ARG_HOST_KEY :
            arguments->host = arg;
            break;

        case ARGP_KEY_ARG:
            return -EINVAL;
            break;

        case ARGP_KEY_END:
            break;

        default:
            return ARGP_ERR_UNKNOWN;
    }
    return 0;
}

/*
//|--------------------------------------------------------------------------
//| Method     : sanity_msg
//|--------------------------------------------------------------------------
*/
static uint16_t sanity_msg(void *buffer)
{
	/*lint --e{613} suppress "msg" is not NULL check */
    struct msgHeaderNt * msg = buffer;
    timestamp_fct(&msg->timeReply);

    /* Multiple bits on the control field, do not blindly overwrite! */
    msg_control_set(&msg->control,MSG_CONTROL_ACK);

    return sizeof(struct msgHeaderNt);
}

/*
//|--------------------------------------------------------------------------
//| Method     : process_msg
//|--------------------------------------------------------------------------
*/
static void process_msg
(
    int    rx_fd,
    void * buffer,
    int    size
)
{
	/*lint --e{613} suppress "header" is not NULL check */
	ssize_t              nbytes;
    struct msgHeaderNt * header = buffer;
    struct sockaddr_un   name_from;
    socklen_t            size_from;
    msgLengthNt          reply_length;
    static msgSequenceNt sequence = 0;

    size_from = sizeof(struct sockaddr_un);
    nbytes = recvfrom(rx_fd, buffer, size, 0,
                       (struct sockaddr *)&name_from, (socklen_t *)&size_from);

    if( nbytes < 0 )
    {
        printf("recvfrom failed : %s\n",strerror(errno));
        return;
    }

    if( header->length != nbytes )
    {
        printf("length expected(%d) != received(%d)\n",header->length,nbytes);
        return;
    }

    /* Get timestamp for start of action */
    timestamp_fct(&header->rxTime);

    reply_length = 0;
    switch( header->type )
    {
        case MSG_COMMON_SANITY:
        {
            printf(" sanity msg recieved by eec");
            reply_length = sanity_msg(buffer);
            break;
        }
        default:
        {
            printf("Unhandled message %d\n",header->type);
            break;
        }
    }

    /* Reply if necessary */
    if( reply_length > 0 )
    {
        /* Add reply timestamp */
        timestamp_fct(&header->timeReply);
        header->sequenceReply = sequence++;
        header->length = reply_length;
        size_from = sizeof(name_from.sun_family) + (socklen_t)strlen(name_from.sun_path);
        if( sendto(rx_fd, buffer, header->length, 0, (struct sockaddr *)&name_from, size_from) < 0 )
        {
            printf("sendto failed : %s\n",strerror(errno));
        }
    }
}

/*
//|--------------------------------------------------------------------------
//| Method     : processing_thread
//|--------------------------------------------------------------------------
*/
static void * processing_thread
(
  void * arg __attribute__ ((unused))
)
{
    void  *  buffer;
    uint32_t max_size;
    fd_set   read_fds;
    int      ret;
    int      max_fd;

    max_size = MSG_MAX_LEN;

    ret = posix_memalign(&buffer, 32, max_size);
    if( ret != 0 )
    {
        printf("Could not allocate memory of size %d : %s\n",
            max_size,strerror(errno));
        return ((void*)EXIT_FAILURE);
    }

    max_fd = MAX(options.local_fd, options.remote_fd) + 1;
    for(;;)
    {
        FD_ZERO(&read_fds);
        if( options.local_fd )
        {
            FD_SET(options.local_fd, &read_fds);
        }
        if( options.remote_fd )
        {
            FD_SET(options.remote_fd, &read_fds);
        }

        ret = select(max_fd, &read_fds, 0, 0, 0);
        if( ret < 0 )
        {
            printf("select failed : %s\n",strerror(errno));
            break;
        }
        else if ( ret == 0 )
        {
            printf("select timed out : %s\n",strerror(errno));
            continue;
        }

        if( options.local_fd && FD_ISSET(options.local_fd, &read_fds) )
        {
            process_msg(options.local_fd, buffer, max_size);
        }

        if( options.remote_fd && FD_ISSET(options.remote_fd, &read_fds) )
        {
            process_msg(options.remote_fd, buffer, max_size);
        }
    }
    return ((void*)EXIT_FAILURE);
}

static struct argp argp = { eec_argp_options, parse_opt, NULL, NULL,0,0,0 };

/*
//|--------------------------------------------------------------------------
//| Method     : main
//|--------------------------------------------------------------------------
*/
int main(int argc, char *argv[])
{
    printf("fim_thread (test) is starting");

    if( argp_parse(&argp, argc, (char **)argv, 0, 0, &options) )
    {
        printf("argp_parse failed : %s\n",strerror(errno));
    }

    /****************** Open local socket ******************/
    size_t size;

    printf("testing: fd_path: %s", options.fd_path);
    if( !options.fd_path )
    {
        printf("fd_path is null \n");
    }
    else
    {
        printf("fd_path: %s", options.fd_path);
    }

    // Create my socket
    options.local_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if( options.local_fd < 0 )
    {
        printf("socket creation failed : %s\n", strerror(errno));
    }
    memset(&options.local_addr, 0, sizeof(options.local_addr));
    options.local_addr.sun_family = AF_UNIX;
    if( options.fd_path == NULL )
    {
        printf("fd_path is null \n");
    }
    else
    {
        strcpy(options.local_addr.sun_path, options.fd_path);
    }
    size = sizeof(options.local_addr.sun_family) +
           strlen(options.local_addr.sun_path);

    if( options.fd_path == NULL )
    {
        printf("fd_path is null \n");
    }
    else
    {
        (void)unlink(options.fd_path);
    }

    if( bind(options.local_fd, (struct sockaddr *)&options.local_addr, size) )
    {
        printf("bind for socket failed : %s\n",strerror(errno));
    }

    (void)processing_thread(0);
    return 0;
}
